use graphics::cosmic_text::{Action, Buffer, Color, Cursor, Edit, Editor, Metrics};
use graphics::{BorderRadii, Box2D, Frame, Point, SimpleVertex, Size, Text, Transform3D, Vector};

use crate::contexts::{EventCtx, LayoutCtx, LifeCycleCtx, PaintCtx};
use crate::event::{Event, Key, LifeCycle, NamedKey};
use crate::id::IdPath;
use crate::widget::Widget;

const PADDING: f32 = 10.0;
const STROKE_WIDTH: f32 = 4.0;
const MIN_WIDTH: f32 = 300.0;

pub struct Input {
    pub(crate) text: Option<Text>,
    id_path: IdPath,
    pub(crate) layout: Option<Editor>,
    offset: f32,
    size: Size,
    inner_size: Size,
    is_drag: bool,
}

impl Input {
    pub fn new(id_path: IdPath) -> Self {
        Self {
            text: None,
            id_path,
            layout: None,
            offset: 0.0,
            size: Default::default(),
            inner_size: Default::default(),
            is_drag: false,
        }
    }
}
impl Widget for Input {
    fn event(&mut self, cx: &mut EventCtx, event: &Event) {
        match event {
            Event::MousePressed(info) => {
                if cx.is_hot() {
                    cx.set_active(true);
                    self.layout.as_mut().map(|editor| {
                        editor.set_cursor(Cursor::default());
                    });
                    let text_mouse_pos = info.pos - Vector::new(PADDING, PADDING);
                    self.layout
                        .as_mut()
                        .zip(self.text.as_mut())
                        .map(|(editor, text)| {
                            text.with_font_system_mut(|font_system| {
                                editor.action(
                                    font_system,
                                    Action::Click {
                                        x: text_mouse_pos.x as i32,
                                        y: text_mouse_pos.y as i32,
                                    },
                                )
                            });
                        });
                    self.is_drag = true;
                    cx.request_paint();
                }

                if cx.is_active() && !cx.is_hot() {
                    cx.set_active(false);
                    self.layout.as_mut().map(|editor| {
                        let mut cursor = editor.cursor();
                        cursor.color = Some(Color::rgba(0, 0, 0, 0));
                        editor.set_cursor(cursor);
                        editor.set_select_opt(None);
                    });
                    cx.request_layout();
                    cx.request_paint();
                }
            }
            Event::MouseReleased(_) => {
                self.is_drag = false;
            }
            Event::MouseMove(info) if self.is_drag => {
                let text_mouse_pos = info.pos - Vector::new(PADDING, PADDING);
                self.layout
                    .as_mut()
                    .zip(self.text.as_mut())
                    .map(|(editor, text)| {
                        text.with_font_system_mut(|font_system| {
                            editor.action(
                                font_system,
                                Action::Drag {
                                    x: text_mouse_pos.x as i32,
                                    y: text_mouse_pos.y as i32,
                                },
                            )
                        });
                    });
            }
            Event::KeyPressed(key) => {
                if cx.is_active() {
                    if let Key::Named(NamedKey::Backspace) = &key.logical_key {
                        self.layout
                            .as_mut()
                            .zip(self.text.as_mut())
                            .map(|(editor, text)| {
                                text.with_font_system_mut(|font_system| {
                                    editor.action(font_system, Action::Backspace);
                                });
                            });
                    } else if let Some(text) = &key.text {
                        for c in text.chars() {
                            self.layout
                                .as_mut()
                                .zip(self.text.as_mut())
                                .map(|(editor, text)| {
                                    text.with_font_system_mut(|font_system| {
                                        editor.action(font_system, Action::Insert(c))
                                    });
                                });
                        }
                    }

                    match &key.logical_key {
                        Key::Named(key) => match key {
                            NamedKey::ArrowLeft => {
                                self.layout.as_mut().zip(self.text.as_mut()).map(
                                    |(editor, text)| {
                                        text.with_font_system_mut(|font_system| {
                                            editor.action(font_system, Action::Left)
                                        });
                                    },
                                );
                            }
                            NamedKey::ArrowRight => {
                                self.layout.as_mut().zip(self.text.as_mut()).map(
                                    |(editor, text)| {
                                        text.with_font_system_mut(|font_system| {
                                            editor.action(font_system, Action::Right)
                                        });
                                    },
                                );
                            }
                            _ => {}
                        },
                        _ => {}
                    }
                    cx.request_layout();
                    cx.request_paint();
                }
            }
            Event::MouseWheel(info) => {
                if cx.is_hot() {
                    self.offset = self.offset + info.wheel_delta.x;
                    cx.request_layout();
                    cx.request_paint();
                }
            }
            _ => {}
        }
    }

    fn paint(&mut self, cx: &mut PaintCtx, g: &mut Frame) {
        let Some(layout) = &mut self.layout else {
            return;
        };
        let rect = Box2D::from_origin_and_size(Point::origin(), self.size);
        if cx.is_active() {
            g.rounded_rect(&rect, &BorderRadii::new(5.0), [0.8, 0.8, 0.8, 1.0]);
        } else if cx.is_hot() {
            g.rounded_rect(&rect, &BorderRadii::new(5.0), [0.9, 0.9, 0.9, 1.0]);
        } else {
            g.rounded_rect(&rect, &BorderRadii::new(5.0), [1.0; 4]);
        }
        g.rounded_rect_stroke(
            &rect,
            &BorderRadii::new(5.0),
            [0.0, 0.0, 0.0, 1.0],
            STROKE_WIDTH,
        );

        let border = Box2D::from_origin_and_size(cx.widget_state.origin, self.size);
        g.set_clip(Some(border));
        g.translate(Vector::new(self.offset, 0.0));
        let mut vertices = Vec::new();
        let mut indices = Vec::new();
        let font_system = g.g.font_system.clone();
        font_system.with_font_system_mut(|font_system| {
            layout.shape_as_needed(font_system);
            let mut cursor = 0;
            layout.draw(
                font_system,
                &mut g.g.swash_cache,
                Color::rgb(0, 0, 0),
                |x, y, w, h, color| {
                    let (w, h) = (w as i32, h as i32);
                    vertices.push(SimpleVertex {
                        position: [x as f32, y as f32, 0.0],
                        color: [
                            color.r() as f32 / 255.0,
                            color.g() as f32 / 255.0,
                            color.b() as f32 / 255.0,
                            color.a() as f32 / 255.0,
                        ],
                    });
                    vertices.push(SimpleVertex {
                        position: [x as f32, (y + h) as f32, 0.0],
                        color: [
                            color.r() as f32 / 255.0,
                            color.g() as f32 / 255.0,
                            color.b() as f32 / 255.0,
                            color.a() as f32 / 255.0,
                        ],
                    });
                    vertices.push(SimpleVertex {
                        position: [(x + w) as f32, (y + h) as f32, 0.0],
                        color: [
                            color.r() as f32 / 255.0,
                            color.g() as f32 / 255.0,
                            color.b() as f32 / 255.0,
                            color.a() as f32 / 255.0,
                        ],
                    });
                    vertices.push(SimpleVertex {
                        position: [(x + w) as f32, y as f32, 0.0],
                        color: [
                            color.r() as f32 / 255.0,
                            color.g() as f32 / 255.0,
                            color.b() as f32 / 255.0,
                            color.a() as f32 / 255.0,
                        ],
                    });
                    indices.push(cursor);
                    indices.push(cursor + 1);
                    indices.push(cursor + 2);
                    indices.push(cursor);
                    indices.push(cursor + 2);
                    indices.push(cursor + 3);
                    cursor += 4
                },
            );
        });
        g.draw(
            &vertices,
            &indices,
            Transform3D::translation(PADDING, PADDING, 0.0),
        );
        g.set_clip(None);
        g.translate(Vector::new(-self.offset, 0.0))
    }

    fn layout(&mut self, cx: &mut LayoutCtx) -> Size {
        let editor = self.layout.get_or_insert_with(|| {
            let mut font_system = cx.brush_cx.clone();
            font_system
                .with_font_system_mut(|font_system| {
                    let mut buffer = Buffer::new(font_system, Metrics::new(24.0, 40.0));
                    buffer.set_size(font_system, f32::INFINITY, f32::INFINITY);
                    let mut editor = Editor::new(buffer);
                    let mut cursor = Cursor::default();
                    cursor.color = Some(Color::rgba(0, 0, 0, 0));
                    editor.set_cursor(cursor);
                    editor
                })
                .unwrap()
        });
        let text = self.text.get_or_insert_with(|| cx.brush_cx.clone());
        text.with_font_system_mut(|font_system| {
            editor.shape_as_needed(font_system);
        });
        let line_size = editor
            .buffer()
            .layout_runs()
            .map(|run| Size::new(run.line_w, run.line_y))
            .next()
            .unwrap_or_default();
        let size = line_size + Size::new(2.0 * PADDING, 2.0 * PADDING);
        self.inner_size = size;
        self.offset = self.offset.max(MIN_WIDTH - self.inner_size.width).min(0.0);
        self.size = Size::new(
            MIN_WIDTH,
            size.height
                .max(editor.buffer().metrics().line_height + 2.0 * PADDING),
        );
        self.size
    }

    fn lifecycle(&mut self, cx: &mut LifeCycleCtx, event: &LifeCycle) {
        if let LifeCycle::HotChanged(_) = event {
            cx.request_paint();
        }
    }
}
